/*
 * Copyright (c) 2017 Lev Walkin <vlm@lionet.info>.
 * All rights reserved.
 * Redistribution and modifications are permitted subject to BSD license.
 */
#ifndef ASN_DISABLE_OER_SUPPORT

#include <NativeEnumerated.h>
#include <asn_internal.h>
#include <errno.h>

static long asn__nativeenumerated_convert(
    const uint8_t* b, const uint8_t* end) {
  unsigned long value;

  /* Perform the sign initialization */
  /* Actually value = -(*b >> 7); gains nothing, yet unreadable! */
  if ((*b >> 7)) {
    value = (unsigned long) (-1);
  } else {
    value = 0;
  }

  /* Conversion engine */
  for (; b < end; b++) {
    value = (value << 8) | *b;
  }

  return value;
}

asn_dec_rval_t NativeEnumerated_decode_oer(
    const asn_codec_ctx_t* opt_codec_ctx, const asn_TYPE_descriptor_t* td,
    const asn_oer_constraints_t* constraints, void** nint_ptr, const void* ptr,
    size_t size) {
  asn_dec_rval_t rval = {RC_OK, 0};
  long* native        = (long*) *nint_ptr;
  const uint8_t* b    = ptr;

  (void) opt_codec_ctx;
  (void) constraints;

  if (size < 1) {
    ASN__DECODE_STARVED;
  }

  if ((*b & 0x80) == 0) {
    /*
     * X.696 (08/2015) #11.2 Short form for Enumerated.
     */
    if (!native) {
      native = (long*) (*nint_ptr = CALLOC(1, sizeof(*native)));
      if (!native) ASN__DECODE_FAILED;
    }

    *native       = *b;
    rval.consumed = 1;
  } else {
    /*
     * X.696 (08/2015) #11.4 Long form for Enumerated.
     */
    size_t length = *b & 0x7f;
    const uint8_t* bend;
    long value;

    if (length < 1 || length > sizeof(*native)) {
      ASN__DECODE_FAILED;
    }
    if ((1 + length) > size) {
      ASN__DECODE_STARVED;
    }
    b++;
    bend = b + length;

    value = asn__nativeenumerated_convert(b, bend);
    if (value < 0) {
      const asn_INTEGER_specifics_t* specs =
          (const asn_INTEGER_specifics_t*) td->specifics;
      if (specs && specs->field_unsigned) {
        ASN__DECODE_FAILED;
      }
    }

    if (!native) {
      native = (long*) (*nint_ptr = CALLOC(1, sizeof(*native)));
      if (!native) ASN__DECODE_FAILED;
    }

    *native = value;

    rval.consumed = (1 + length);
  }

  return rval;
}

/*
 * Encode as Canonical OER.
 */
asn_enc_rval_t NativeEnumerated_encode_oer(
    const asn_TYPE_descriptor_t* td, const asn_oer_constraints_t* constraints,
    const void* sptr, asn_app_consume_bytes_f* cb, void* app_key) {
  asn_enc_rval_t er;
  long native;

  (void) constraints;

  if (!sptr) ASN__ENCODE_FAILED;

  native = *(const long*) sptr;

  if (native >= 0 && native <= 127) {
    /* #11.2 Short form */
    uint8_t b  = native;
    er.encoded = 1;
    if (cb(&b, er.encoded, app_key) < 0) {
      ASN__ENCODE_FAILED;
    }
    ASN__ENCODED_OK(er);
  } else {
    /* #11.2 Long form */
    uint8_t buf[1 + sizeof(native)];
    uint8_t* b         = &buf[sizeof(native)]; /* Last addressable */
    long final_pattern = -1 * (native < 0);

    for (;;) {
      *b-- = native;
      native >>= 8;
      if (native == final_pattern) {
        if (final_pattern) {
          if ((b[1] & 0x80)) break;
        } else {
          if (!(b[1] & 0x80)) break;
        }
      }
    }
    *b         = 0x80 | (&buf[sizeof(native)] - b);
    er.encoded = 1 + (&buf[sizeof(native)] - b);
    if (cb(b, er.encoded, app_key) < 0) {
      ASN__ENCODE_FAILED;
    }
    ASN__ENCODED_OK(er);
  }
}

#endif /* ASN_DISABLE_OER_SUPPORT */
